/**
 * @file show_png_image.c
 * @author your name (you@domain.com)
 * @brief libpng 使用示例
 * @version 0.1
 * @date 2023-06-26
 * 
 * @copyright Copyright (c) 2023
 * 
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <png.h>

static int width; /* LCD X 分辨率 */
static int height; /* LCD Y 分辨率 */
static unsigned short *screen_base = NULL; /* 映射后显存基地址 */
static unsigned long line_length; /* LCD 一行的长度(字节为单位) */
static unsigned int bpp; /* 像素深度 */

static int show_png_image(const char *path)
{
    png_structp png_ptr = NULL;
    png_infop info_ptr = NULL;
    FILE *png_file = NULL;
    unsigned short *fb_line_buf = NULL; /* 行缓冲：存储写入到LCD显存的一行数据 */
    unsigned int min_height, min_width;
    unsigned int valid_bytes;
    unsigned int image_height, image_width;
    png_bytepp row_pointers = NULL;

    /* 打开png文件 */
    png_file = fopen(path, "r");
    if (NULL == png_file) {
        perror("fopen error");
        return -1;
    }
    /* 分配和初始化 png_ptr,info_ptr */
    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_ptr) {
        fclose(png_file);
        return -1;
    }
    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
        png_destroy_read_struct(&png_ptr, NULL, NULL);
        fclose(png_file);
        return -1;
    }
    /* 设置错误返回点 */
    if (setjmp(png_jmpbuf(png_ptr))) {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        fclose(png_file);
        return -1;
    }
    /* 指定数据源 */
    png_init_io(png_ptr, png_file);
    /* 读取png文件 */
    png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_FILLER_AFTER, NULL);
    image_height = png_get_image_height(png_ptr, info_ptr);
    image_width = png_get_image_width(png_ptr, info_ptr);
    printf("分辨率：%d*%d\n", image_width, image_height);
    /* 判断是不是 RGB888 */
    if ((8 != png_get_bit_depth(png_ptr, info_ptr)) &&
        (PNG_COLOR_TYPE_RGB != png_get_color_type(png_ptr, info_ptr))) {
        printf("Error: Not 8bit depth or not RGB888 color\n");
        png_destory_read_struct(&png_ptr, &info_ptr, NULL);
        fclose(png_file);
        return -1;
    }
    /* 判断图像和LCD屏幕哪个的分辨率更低 */
    if (image_width > width) {
        min_width = width;
    } else {
        min_width = image_width;
    }
    if (image_height > height) {
        min_height = height;
    } else {
        min_height = image_height;
    }
    valid_bytes = min_width * bpp / 8;
    /* 读取解码后的数据 */
    fb_line_buf = malloc(valid_bytes);
    row_pointers = png_get_rows(png_ptr, info_ptr); /* 获取数据 */
    unsigned int temp = min_width * 3; /* RGB888一个像素3个bit位 */
    for (int i = 0; i < min_height; i++) {
        /* RGB888 转为 RGB565 */
        for (int j = 0, k = 0; j < temp; j += 3, k++) {
            fb_line_buf[k] = ((row_pointers[i][j] & 0xF8) << 8) |
                            ((row_pointers[i][j+1] & 0xFC) << 3) |
                            ((row_pointers[i][j+2] & 0xF8) >> 3);
        }
        memcpy(screen_base, fb_line_buf, valid_bytes); /* 将一行数据刷入显存 */
        screen_base += width; /* 定位到显存下一行 */
    }
    /* 结束、销毁、释放内存  */
    png_destory_read_struct(&png_ptr, &info_ptr, NULL);
    free(fb_line_buf);
    fclose(png_file);
    return 0;
}

int main(int argc, char const *argv[])
{
    struct fb_fix_screeninfo fb_fix;
    struct fb_var_screeninfo fb_var;
    unsigned int screen_size;
    int fd;
    
    /* 传参校验 */
    if (argc != 2) {
        fprintf(stderr, "usage: %s<png_file>\n", argv[0]);
        exit(-1);
    }
    /* 打开 framebuffer设备 */
    if (0 > (fd = open("/dev/fb0", O_RDWR))) {
        perror("open error");
        exit(EXIT_FAILURE);
    }
    /* 获取参数信息 */
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    line_length = fb_fix.line_length;
    bpp = fb_var.bits_per_pixel;
    screen_size = line_length * fb_var.yres;
    width = fb_var.xres;
    height = fb_var.yres;
    /* 将显示缓冲区映射到进程地址空间 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == (void *)screen_base) {
        perror("mmap error");
        close(fd);
        exit(EXIT_FAILURE);
    }
    /* 显示 png图片 */
    memset(screen_base, 0xff, screen_size); /* 屏幕刷白 */
    show_png_image(argv[1]);
    /* 退出 */
    munmap(screen_base, screen_size); /* 取消映射 */
    close(fd);
    exit(EXIT_SUCCESS);
}
